package com.fyz.aaronfjava.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

/**
 * jwt工具类
 * header和payload可以直接利用base64解码出原文，从header中获取哈希签名的算法，从payload中获取有效数据
 * signature使用了不可逆的加密算法，无法解码出原文，它的作用是校验token有没有被篡改。
 * 服务端获取header中的加密算法之后，利用该算法加上secretKey对header、payload进行加密，比对加密后的数据和客户端发送过来的是否一致。
 * 注意secretKey只能保存在服务端，而且对于不同的加密算法其含义有所不同，secretKey实际上代表的是盐值
 */
public class Jwt {

    /**
     * 密钥
     */
    private static final String JWT_SECRET = Base64.encodeBase64String("fanyaozhou".getBytes(StandardCharsets.UTF_8));
    /**
     * 过期时间 1小时
     */
    private static final long EXPIRE_TIME = 60 * 60 * 1000;

    /**
     * 签发人
     */
    private static final String ISSUER = "fanyaozhou";

    /**
     * 生成一个随机的jwt
     * @return
     */
    public static String random(){
        Map<String, Object> map = new HashMap<>();
        //随机数
        map.put("randomNum",ThreadLocalRandom.current().nextLong());
        return create(null,map);
    }

    /**
     * 权限验证主题
     */
    public static final String ROLE_SUBJECT = "role";
    /**
     * 等级 对应的是一个整数
     */
    public static final String LEVEL = "level";
    /**
     * 角色
     */
    public static final String ROLE = "role";

    /**
     * 创建主题为role（身份）的jwt
     * @param level
     * @param role
     * @return
     */
    public static String createByRole(Integer level, String role){
        Map<String,Object> map = new HashMap<>();
        map.put(LEVEL,level);
        map.put(ROLE,role);
        return create(ROLE_SUBJECT,map);
    }

    public static String create(String subject, Map<String, Object> map) {
        return create(ISSUER, subject, map);
    }

    /**
     * 生成jwt
     * 需要注意，载荷中尽量不要出现键为sub键值对，因为会被覆盖(还有其他一些，尽量避开claims预定义好的载荷字段)
     *
     * @param issuer  签发人信息
     * @param subject 主题 JWT声明的对象
     * @param claims  载荷 一般是对主题的描述
     * @return
     */
    public static String create(String issuer, String subject, Map<String, Object> claims) {
        //载荷map不能为null(可以没有键值对)
        if (claims == null) {
            return null;
        }
        //设置jwt的信息
        JwtBuilder builder = Jwts.builder()
                //设置载荷 优先设置
                .setClaims(claims)
                //设置一个唯一标识 防止jwt重复 字段jti
                .setId(UUID.randomUUID().toString())
                //jwt的签发时间 字段iat
                .setIssuedAt(new Date(System.currentTimeMillis()))
                //jwt签发者 字段iss
                .setIssuer(issuer)
                //jwt主题 字段sub
                .setSubject(subject)
                //设置签名 会将签名算法嵌入其中 HS256是一种对称加密
                .signWith(SignatureAlgorithm.HS256, JWT_SECRET);
        //返回jwt字符串
        return builder.compact();
    }

    /**
     * 解析token
     *
     * @param token
     * @return 载荷信息
     */
    public static Claims parse(String token) {
        return Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody();
    }

    /**
     * 判断token是否合法
     *
     * @param token
     * @return
     */
    public static boolean validate(String token) {
        if (StringUtil.isBlank(token)){
            return false;
        }
        Claims claims = parse(token);
        //判断签发人是否正确
        if (!ISSUER.equals(claims.getIssuer())) {
            return false;
        }
        //判断是否过期
        //获取签发时间
        long issueDate = claims.getIssuedAt().getTime();
        return ((issueDate + EXPIRE_TIME) - System.currentTimeMillis()) > 0;
    }

}

